/**
 * Project Name:RSAUtils.java
 * File Name:RSAUtils.java
 * Date:2018年06月28日 15:27
 * Copyright (c) 2018, zhang.xiangyu@foxmail.com All Rights Reserved.
 */
package com.meatball.token.sdk.util;

import com.meatball.token.sdk.model.RSAKeys;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**   
 * @Title: RSAUtils
 * @Description: todo(
 * 非对称加密算法RSA算法组件
 * 非对称算法一般是用来传送对称加密算法的密钥来使用的 ， 相对于DH算法 ， RSA算法只需要一方构造密钥 ， 不需要
 * 大费周章的构造各自本地的密钥对了 。 DH算法只能算法非对称算法的底层实现 。 而RSA算法算法实现起来较为简单
 * )
 * @Author 張翔宇
 * @Date 2018年06月29日 16:24
 */
public class RSAUtils {

    /**
     * 非对称密钥算法
     */
    public static final String KEY_ALGORITHM = "RSA";

    /**
     * 密钥长度，DH算法的默认密钥长度是1024
     * 密钥长度必须是64的倍数，在512到65536位之间
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * 最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 公钥
     */
    private static final String PUBLIC_KEY = UUIDUtil.uuid();

    /**
     * 私钥
     */
    private static final String PRIVATE_KEY = UUIDUtil.uuid();

    /**
     * 初始化密钥对
     *
     * @return Map 甲方密钥的Map
     */
    public static Map<String, Object> initKey() throws Exception {
        //实例化密钥生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE);
        //生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //甲方公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //甲方私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        //将密钥存储在map中
        Map<String, Object> keyMap = new HashMap<String, Object>();
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;

    }


    /**
     * 私钥加密
     * @param data 待加密数据
     * @param key 私钥
     * @return 加密后的字符串
     * @throws Exception
     */
    public static String encryptByPrivateKey(String data, String key) throws Exception {
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return Base64.encodeBase64String(RSAUtils.doFinalUtil(data.getBytes(), cipher, MAX_ENCRYPT_BLOCK));
    }

    /**
     * 公钥加密
     *
     * @param data 待加密数据
     * @param key       密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPublicKey(String data, byte[] key) throws Exception {
        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);

        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return cipher.doFinal(data.getBytes());
    }

    /**
     * 私钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     * @param data 待解密数据
     * @param key 公钥
     * @return
     * @throws Exception
     */
    public static String decryptByPublicKey(String data, String key) throws Exception {

        //实例化密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //初始化公钥
        //密钥材料转换
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(key));
        //产生公钥
        PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
        //数据解密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        return new String(RSAUtils.doFinalUtil(Base64.decodeBase64(data), cipher, MAX_DECRYPT_BLOCK));
    }

    /**
     * 取得私钥
     *
     * @return byte[] 私钥
     */
    /*private static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }*/

    /**
     * 取得公钥
     *
     * @return byte[] 公钥
     */
    /*private static String getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }*/

    /**
     * 获取密钥串
     * @return
     * @throws Exception
     */
    /*public static RSAKeys getKeys() {
        RSAKeys params = new RSAKeys();
        //初始化密钥
        //生成密钥对
        Map<String, Object> keyMap = null;
        try {
            keyMap = RSAUtils.initKey();
            params.setPublicKey(RSAUtils.getPublicKey(keyMap));
            params.setPrivateKey(RSAUtils.getPrivateKey(keyMap));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return params;
    }*/

    /**
     * 执行分段操作
     * @param data 待加解密数据
     * @param cipher
     * @param max 最大长度
     * @return
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    private static byte[] doFinalUtil(byte[] data, Cipher cipher, int max) throws BadPaddingException, IllegalBlockSizeException {
        byte[] cache = new byte[0];
        int dataLength = data.length, offset = 0, i = 1;
        while (dataLength - offset > 0) {
            if(dataLength -offset > max) {
                cache = ArrayUtils.addAll(cache, cipher.doFinal(data, offset, max));
            } else {
                cache = ArrayUtils.addAll(cache, cipher.doFinal(data, offset, dataLength - offset));
            }
            offset = i * max;
            i++;
        }
        return cache;
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        /*System.out.println("================密钥对构造完毕,甲方将公钥公布给乙方，开始进行加密数据的传输=============");
        String str = "{\"pageNum\":1,\"pageSize\":10,\"size\":2,\"startRow\":1,\"endRow\":2,\"total\":2,\"pages\":1,\"list\":[{\"offset\":null,\"limit\":null,\"order\":null,\"id\":26,\"avatar\":\"\",\"account\":\"demo\",\"password\":\"7f562d0ed0d423ceb4a1f4c7ff479ec5\",\"resetPwd\":false,\"salt\":\"jmpt6mfricervsutnrn13sbbx100bry3q7n9yecmyy0kc\",\"name\":\"测试\",\"birthday\":\"2018-06-27T16:00:00.000+0000\",\"birthdayText\":\"2018-06-28\",\"sex\":0,\"sexName\":\"保密\",\"email\":\"123@123.com\",\"phone\":\"13888888888\",\"roleid\":null,\"deptid\":null,\"status\":1,\"statusName\":\"启用\",\"statusType\":false,\"createtime\":\"2018-06-28T02:53:10.000+0000\",\"createtimeText\":null,\"version\":null,\"verify\":null,\"rememberMe\":false,\"publicKey\":null,\"privateKey\":null,\"appid\":null,\"rolesVo\":null,\"menusVo\":null,\"loginUser\":null},{\"offset\":null,\"limit\":null,\"order\":null,\"id\":3,\"avatar\":\"MjAxOC0wNi0xMS8xNTI4NzE4ODAxNzQ2LnBuZw==.png\",\"account\":\"admin\",\"password\":\"fb47bd4c102dd63dfc4202ae6ae4b5cc\",\"resetPwd\":false,\"salt\":\"afd!@#DCF,123\",\"name\":\"系统管理员\",\"birthday\":\"2018-01-10T16:00:00.000+0000\",\"birthdayText\":\"2018-01-11\",\"sex\":2,\"sexName\":\"女\",\"email\":\"admin@meatball.com\",\"phone\":\"13888888888\",\"roleid\":null,\"deptid\":null,\"status\":1,\"statusName\":\"启用\",\"statusType\":false,\"createtime\":\"2017-01-10T06:41:24.000+0000\",\"createtimeText\":null,\"version\":null,\"verify\":null,\"rememberMe\":false,\"publicKey\":null,\"privateKey\":null,\"appid\":null,\"rolesVo\":null,\"menusVo\":null,\"loginUser\":null}],\"prePage\":0,\"nextPage\":0,\"isFirstPage\":true,\"isLastPage\":true,\"hasPreviousPage\":false,\"hasNextPage\":false,\"navigatePages\":8,\"navigatepageNums\":[1],\"navigateFirstPage\":1,\"navigateLastPage\":1,\"firstPage\":1,\"lastPage\":1}";
        System.out.println("===========甲方向乙方发送加密数据==============");
        System.out.println("原文:" + str);
        //甲方进行数据的加密
        RSAKeys rsaKeys = RSAUtils.getKeys();
        String code1 = RSAUtils.encryptByPrivateKey(str, rsaKeys.getPrivateKey());
        System.out.println("加密后的数据：" + code1);
        System.out.println("===========乙方使用甲方提供的公钥对数据进行解密==============");
        //乙方进行数据的解密
        String decode1 = RSAUtils.decryptByPublicKey(code1, rsaKeys.getPublicKey());
        System.out.println("乙方解密后的数据：" + decode1);*/

        /*System.out.println("===========反向进行操作，乙方向甲方发送数据==============");

        str = "乙方向甲方发送数据RSA算法";

        System.out.println("原文:" + str);

        //乙方使用公钥对数据进行加密
        byte[] code2 = RSAUtils.encryptByPublicKey(str.getBytes(), publicKey);
        System.out.println("===========乙方使用公钥对数据进行加密==============");
        System.out.println("加密后的数据：" + Base64.encodeBase64String(code2));

        System.out.println("=============乙方将数据传送给甲方======================");
        System.out.println("===========甲方使用私钥对数据进行解密==============");

        //甲方使用私钥对数据进行解密
        byte[] decode2 = RSAUtils.decryptByPrivateKey(code2, privateKey);

        System.out.println("甲方解密后的数据：" + new String(decode2));*/
    }
}
